-module(rtps_participant_SUITE).

%%% ============================================================================
%%% Create two participants, local and remote and check if they detect
%%% one another. Nice, if you have a separate networked system as a
%%% remote.
%%% ============================================================================

-include_lib("common_test/include/ct.hrl").
-include("../src/rtps.hrl").

-export([all/0, suite/0, groups/0,
	 init_per_suite/1, end_per_suite/1, 
	 init_per_group/2, end_per_group/2,
	 init_per_testcase/2, end_per_testcase/2]).

-compile(export_all).

%% To run the tests using a remote system, install RTPS on the remote
%% system and configure ssh in such a way that the user executing this
%% test can login without password authentication, i.e. use preshared
%% keys (man ssh-copy-id).

%%-define(REMOTE_HOST, undefined). % when there is no remote system for running the tests
-define(REMOTE_HOST, rpi3). % A Raspberry Pi 3 Model B+
-define(REMOTE_NODE_NAME, b).

-define(DOMAIN_ID, 0).
-define(GUID_PREFIX_1, <<1:96>>).
-define(GUID_PREFIX_2, <<2:96>>).


suite() ->
    [{timetrap, {minutes, 1}}].

all() -> 
    [{group, local},
     {group, remote}
    ].

groups() ->
    [{local,       [], [participant]},
     {remote,      [], [participant]}
    ].

init_per_suite(Config) ->
    ok = application:start(rtps),
    Config.

end_per_suite(_Config) ->
    application:stop(rtps),
    ok.

%%%===================================================================
%%% Init per group
%%%===================================================================

init_per_group(local, Config) ->
    %% Running the tests locally, requires the UDP socket to operate
    %% in multicast loopback mode.
    Opts = [{multicast_loop, true}],
    {ok, _} = add_domain(local, ?DOMAIN_ID, Opts),
    [{node, local} | Config];
init_per_group(remote, _Config) when ?REMOTE_HOST =:= undefined ->
    {skip, "Remote not configured"};
init_per_group(remote, Config) ->
    case net_kernel:getopts(node(), []) of
	ignored ->
	    {ok, _} = net_kernel:start([?MODULE, shortnames]);
	_ ->
	    ok
    end,
    Here = filename:dirname(code:which(?MODULE)),
    Ebin = filename:join([Here, "..", "ebin"]),
    Paths = [Here, Ebin],
    Node = start_remote(?REMOTE_HOST, ?REMOTE_NODE_NAME, Paths),
    %% Make SPDP announcements at a higher rate than the default 30
    %% seconds interval to make the tests run faster.
    Opts = [{resend_period, 500}],
    {ok, _} = add_domain(local, ?DOMAIN_ID, Opts),
    {ok, _} = add_domain(Node, ?DOMAIN_ID, Opts),
    [{node, Node}, {opts, []} | Config];
init_per_group(_Group, Config) ->
    Config.

%%%===================================================================
%%% End per group
%%%===================================================================

end_per_group(remote, Config) ->
    Node = ?config(node, Config),
    ok = del_domain(Node, ?DOMAIN_ID),
    stop_remote(Node),
    Config;
end_per_group(_Group, Config) ->
    ok = del_domain(local, ?DOMAIN_ID),
    Config.

%%%===================================================================
%%% Init per case
%%%===================================================================

init_per_testcase(_Case, Config) ->
    Config.

%%%===================================================================
%%% End per case
%%%===================================================================

end_per_testcase(_Case, _Config) ->
    ok.

%%%===================================================================
%%% Test cases
%%%===================================================================

participant() ->
    [{timetrap, 10000}]. % ms

%% @doc Check starting and stopping a participant
participant(Config) ->
    Node = ?config(node, Config),
    {ok, _} = add_participant(local, ?DOMAIN_ID, ?GUID_PREFIX_1),
    {ok, _} = add_participant(Node, ?DOMAIN_ID, ?GUID_PREFIX_2),
    ok = check_conn(local, ?DOMAIN_ID, ?GUID_PREFIX_1, ?GUID_PREFIX_2),
    ok = check_conn(Node, ?DOMAIN_ID, ?GUID_PREFIX_2, ?GUID_PREFIX_1).

%%%===================================================================
%%% Internal functions
%%%===================================================================

add_domain(Node, Domain_id) ->
    add_domain(Node, Domain_id, []).

add_domain(local, Domain_id, Opts) ->
    rtps:add_domain(Domain_id, Opts);
add_domain(Node, Domain_id, Opts) ->
    ct_rpc:call(Node, rtps, add_domain, [Domain_id, Opts]).


del_domain(local, Domain_id) ->
    rtps:del_domain(Domain_id);
del_domain(Node, Domain_id) ->
    ct_rpc:call(Node, rtps, del_domain, [Domain_id]).


add_participant(local, Domain_id, Guid_prefix) ->
    {ok, _} = rtps:add_participant(Domain_id, Guid_prefix);
add_participant(Node, Domain_id, Guid_prefix) ->
    {ok, _} = ct_rpc:call(Node, rtps, add_participant, [Domain_id, Guid_prefix]).
    
info(local, Domain_id, Guid_prefix) ->
    rtps:info(Domain_id, Guid_prefix);
info(Node, Domain_id, Guid_prefix) ->
    ct_rpc:call(Node, rtps, info, [Domain_id, Guid_prefix]).
    
remotes(Node, Domain_id, Guid_prefix) ->
    Info = info(Node, Domain_id, Guid_prefix),
    proplists:get_value(remotes, Info).

check_conn(Node, Domain_id, Guid_prefix1, Guid_prefix2) ->
    Remotes = remotes(Node, Domain_id, Guid_prefix1),
    case lists:member(Guid_prefix2, Remotes) of
	true ->
	    ok;
	false ->
	    timer:sleep(100),
	    check_conn(Node, Domain_id, Guid_prefix1, Guid_prefix2)
    end.

start_remote(Host, Node_name, Paths) ->
    {ok, Node} = ct_slave:start(Host, Node_name, [{monitor_master, true}]),
    ok = ct_rpc:call(Node, code, add_pathsa, [Paths]),
    ok = ct_rpc:call(Node, application, start, [rtps]),
    Node.

stop_remote(Node) ->
    ct_rpc:call(Node, application, stop, [rtps]),
    ct_slave:stop(Node).
